package site.psvm.phoneinfo.common.util;

import com.google.common.base.Preconditions;

import java.util.Calendar;
import java.util.Random;

/**
 * SnowflakeKeyGenerator雪花算法生成唯一码
 */
public final class UniqueCodeGender {

    private static final long EPOCH;

    private static final long SEQUENCE_BITS = 12L;

    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    private long workerId = 0l;  //0~1023

    private int maxVibrationOffset = 1;

    private int maxTolerateTimeDifferenceMilliseconds = 10;

    private int sequenceOffset = -1;

    private long sequence;

    private long lastMilliseconds;

    private static UniqueCodeGender instance;

    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    private UniqueCodeGender() {
        Random random = new Random();
        this.workerId = (long)random.nextInt(1024);
    }

    public static long getWorkerId() {
        if(instance==null){
            synchronized (UniqueCodeGender.class){
                if(instance==null){
                    instance = new UniqueCodeGender();
                }
            }
        }
        return instance.workerId;
    }

    private void setWorkerId(long newWorkerId){
        this.workerId = newWorkerId;
    }

    public synchronized static Number generateKey() {
        if(instance==null){
            instance = new UniqueCodeGender();
        }
        return instance._generateKey();
    }

    public synchronized static void resetWorkerId(long newWorkerId){
        if(instance==null){
            instance = new UniqueCodeGender();
        }
        instance.setWorkerId(newWorkerId);
    }

    private Number _generateKey(){
        long currentMilliseconds = System.currentTimeMillis();
        if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
            currentMilliseconds = System.currentTimeMillis();
        }
        if (lastMilliseconds == currentMilliseconds) {
            if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {
                currentMilliseconds = waitUntilNextTime(currentMilliseconds);
            }
        } else {
            vibrateSequenceOffset();
            sequence = sequenceOffset;
        }
        lastMilliseconds = currentMilliseconds;
        return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }

    private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {
        if (lastMilliseconds <= currentMilliseconds) {
            return false;
        }
        long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;
        Preconditions.checkState(timeDifferenceMilliseconds < maxTolerateTimeDifferenceMilliseconds,
                "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastMilliseconds, currentMilliseconds);
        try {
            Thread.sleep(timeDifferenceMilliseconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    private long waitUntilNextTime(final long lastTime) {
        long result = System.currentTimeMillis();
        while (result <= lastTime) {
            result = System.currentTimeMillis();
        }
        return result;
    }

    private void vibrateSequenceOffset() {
        sequenceOffset = sequenceOffset >= maxVibrationOffset ? 0 : sequenceOffset + 1;
    }
}
